package btb.net;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;

import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.kryonet.Server;
import com.esotericsoftware.minlog.Log;

import btb.db.CDatabase;
import btb.engine.ATask;
import btb.engine.CEvent;
import btb.engine.CTaskMessenger;
import btb.net.CNetwork.NAuthenticate;
import btb.net.CNetwork.NClientList;
import btb.net.CNetwork.NRegister;
import btb.net.CNetwork.NRegisterClient;
import btb.net.CNetwork.NRegisterMeta;
import btb.net.CNetwork.NRegisterServer;
import btb.net.CNetwork.NServerList;
import btb.net.CNetwork.NUpdate;
import btb.net.CNetwork.NUpdateChannelList;
import btb.net.CNetwork.NUpdateUserList;
import btb.net.CNetwork.NUpdateServerList;
import btb.net.CNetwork.NValidate;
import btb.util.CLogger;

public class CMetaServer extends ATask
{
	protected Server server = null;
	protected String name = "Unknown";
	
	protected int networkType = CNetwork.META_SERVER; 
	
	protected CDatabase db = null;
	
	protected int validationMS = 5000;
	
	protected NServerList serverList = null;
	protected NClientList clientList = null;
	
	protected static class NMetaConnection extends Connection
	{
		//public int networkType = CNetwork.NULL;
		public String name = "Unknown";
		//public NInfo info = null;
		
		public long lastValidation;
		
		/*public boolean needsValidation() {
			return this.isConnected() && System.currentTimeMillis() - this.lastValidation > CMetaServer.this.validationMS;
		}*/
	}
	
	protected Listener metaListener = new Listener()
	{	
		public void received(Connection _c, Object _o)
		{
			NMetaConnection connection = (NMetaConnection) _c;
			
			if(!CMetaServer.this.serverList.contains(connection) && !CMetaServer.this.clientList.contains(connection))
			{
				if(_o instanceof NRegister)
					CMetaServer.this.register(connection, (NRegister)_o);
				else if(_o instanceof NAuthenticate)
				{
					CMetaServer.this.authenticate(_c, (NAuthenticate)_o);
				}
			}
			else if(CMetaServer.this.serverList.contains(connection))
			{
				if(_o instanceof NUpdate)
					CMetaServer.this.update(connection, (NUpdate)_o);
			}
			else if(CMetaServer.this.clientList.contains(connection))
			{
				if(_o instanceof NUpdate)
				{
					
				}
			} 
			else
			{
				
			}
		}
		
		public void disconnected(Connection _c)
		{
			NMetaConnection connection = (NMetaConnection) _c;
			
			
			if(CMetaServer.this.serverList.contains(connection))
			{
				
			}
			else if(CMetaServer.this.clientList.contains(connection))
			{
				unlistUser(connection.name);
			}
		}
	};
	
	/*protected static class NInfo {}
	
	protected static class NServerInfo extends NInfo {
		public String IP;
	}
	
	protected static class NClientInfo extends NInfo {
		
	}*/
	
	public CMetaServer(ATask _parentTask, int _taskId, CTaskMessenger _mailbox) {
		super(_parentTask, _taskId, _mailbox);
	}
	
	public void register(Connection _c, NRegister _r)
	{
		NMetaConnection connection = (NMetaConnection)_c;
		NRegisterMeta register = (NRegisterMeta)_r;
		
		if(register.networkType == CNetwork.NULL)
			return;
		
		connection.name = register.name;
		
		if(_r instanceof NRegisterClient) {
			this.clientList.add(connection);
									
			this.updateClientList();
		} else if(_r instanceof NRegisterServer) {
			this.serverList.add(connection);
									
			this.updateServerList();
		}
	}
	
	public void authenticate(Connection _c, NAuthenticate _a)
	{
		String password = this.db.getItem("beatthebard.user_accounts", "password", "account_name = '" + _a.user + "'");
		
		if(password.equals(_a.pass))
		{
			listUser(_a.user, _c);
			
			String validationKey = Long.toHexString(Double.doubleToLongBits(Math.random()));
			
			this.db.executeUpdate("update beatthebard.user_accounts set validation_key = '" + validationKey + "' where account_name = '" + _a.user + "'");
			
			//Check to make sure the database has the correct validation key...
			String keyCheck = this.db.getItem("beatthebard.user_accounts", "validation_key", "account_name = '" + _a.user + "'");
			if(!keyCheck.equals(validationKey))
			{
				CLogger.cond_warn("net", "Database-side validation key does not equal generated validation key: " + validationKey + ", " + keyCheck);
			}
			
			NValidate validation = new NValidate();
			validation.validationKey = validationKey;
			_c.sendTCP(validation);
		} 
		else
		{
			String validationKey = "FAIL";
			
			NValidate validation = new NValidate();
			validation.validationKey = validationKey;
			_c.sendTCP(validation);
		}
	}
	
	public void listUser(String _username, Connection _c)
	{
		CUser newUser = new CUser(_username, _c);
		
		NMetaConnection nmc = (NMetaConnection) _c;
		nmc.name = newUser.getProfile().toString();
		
		clientList.add(newUser);
	}
	
	public void unlistUser(String _username)
	{
		clientList.removeByIndex(clientList.findClientByName(_username));
	}
	
	public void handleDeath() {
		
	}
	
	public void handleEvent(CEvent _event) {
		
	}
	
	public void setFriendlyName(String _name) {
		this.name = _name;
	}
	
	public boolean setUp()
	{
		boolean success = true;
		
		//initialize data structures
		this.clientList = new NClientList();
		this.serverList = new NServerList();
		
		//initialize and handle database connection		
		try
		{
			this.db = new CDatabase();
			if(!db.connect())
			{
				success = false;
				if(Log.DEBUG)
					Log.debug("metaserver", "Setup() failure: db.connect() returned false.");
			}
		}
		catch(ClassNotFoundException cnfe)
		{
			success = false;
			if(Log.DEBUG)
				Log.debug("metaserver", "Setup() failure: db.connect() tried; ClassNotFoundException caught.");
			cnfe.printStackTrace();
		}
		
		//initialize and handle server details
		this.server = new Server() {
			protected Connection newConnection() {
				return new NMetaConnection();
			}
		};
		
		CNetwork.register(this.server);
		
		this.server.addListener(this.metaListener);
		
		try
		{
			this.server.bind(CNetwork.META_SERVER_PORT);
			this.server.start();
		}
		catch (IOException ioe)
		{
			success = false;
			if(Log.DEBUG)
				Log.debug("metaserver", "Setup() failure: bind() and start() tried; IOException caught.");
		}
		
		return success;
	}
	
	public boolean tearDown()
	{
		return true;
	}
	
	public void update() {

	}
	
	public void update(Connection _c, NUpdate _u) {		
		if(_u instanceof NUpdateUserList) {
			this.updateClientList();
		} else if(_u instanceof NUpdateServerList) {
			this.updateServerList();
		}
	}
	
	public void updateServerList() {
		Object[] servers = this.serverList.getList();
		ArrayList<String> names = new ArrayList<String>(servers.length);
		
		for(int i = 0; i < servers.length; i++) {
			NMetaConnection connection = (NMetaConnection)servers[i];
			names.add(connection.name);
		}
		
		NUpdateServerList updateServerList = new NUpdateServerList();
		updateServerList.names = (String[])names.toArray(new String[servers.length]);
		
		this.server.sendToAllTCP(updateServerList);
	}
	
	public void updateClientList() {
		Object[] clients = this.clientList.getList();
		ArrayList<String> names = new ArrayList<String>(clients.length);
		
		for(int i = 0; i < clients.length; i++) {
			NMetaConnection connection = (NMetaConnection)clients[i];
			names.add(connection.name);
		}
		
		NUpdateUserList updateClientList = new NUpdateUserList();
		updateClientList.names = (String[])names.toArray(new String[clients.length]);
		
		this.server.sendToAllTCP(updateClientList);
	}
}